#include "globals.h"
#include "errno.h"
#include "types.h"

#include "mm/mm.h"
#include "mm/tlb.h"
#include "mm/mman.h"
#include "mm/page.h"

#include "proc/proc.h"

#include "util/string.h"
#include "util/debug.h"

#include "fs/vnode.h"
#include "fs/vfs.h"
#include "fs/file.h"

#include "vm/vmmap.h"
#include "vm/mmap.h"

/*
 * This function implements the mmap(2) syscall, but only
 * supports the MAP_SHARED, MAP_PRIVATE, MAP_FIXED, and
 * MAP_ANON flags.
 *
 * Add a mapping to the current process's address space.
 * You need to do some error checking; see the ERRORS section
 * of the manpage for the problems you should anticipate.
 * After error checking most of the work of this function is
 * done by vmmap_map(), but remember to clear the TLB.
 */
int
do_mmap(void *addr, size_t len, int prot, int flags,
        int fd, off_t off, void **ret)
{
	KASSERT(NULL != curproc->p_pagedir);
	dbg(DBG_TEST, "KASSERT PASS\n");

	int retval = 0;
	file_t *the_file = NULL;
	vnode_t *the_vnode = NULL;
	vmarea_t *the_vmarea = NULL;

	if (len == 0) {
		return -EINVAL; 
	}
	if (prot & ~(PROT_WRITE | PROT_EXEC | PROT_READ)) {
		return -EINVAL; 
	}
	if (!((flags & MAP_PRIVATE) || (flags & MAP_SHARED))) {
		return -EINVAL; 
	}
	if (flags & MAP_FIXED) {
		if (!PAGE_ALIGNED(addr)) {
			return -EINVAL;
		}
		if (addr < (void *)USER_MEM_LOW || addr >= (void *)USER_MEM_HIGH) {
			return -EINVAL; 
		}
	}
	else {
		addr = 0;
	}
	if (flags & MAP_ANON) {
		the_vnode = NULL;
	}
	else {
		if (fd < 0 || fd >= NFILES) {
			return -EINVAL; 
		}
		the_file = fget(fd);
		if (the_file == NULL) {
			return -EINVAL; 
		}
		the_vnode = the_file->f_vnode;
	}
	if (!PAGE_ALIGNED(off)) {
		return -EINVAL;
	}
       retval = vmmap_map(curproc->p_vmmap, the_vnode, ADDR_TO_PN(addr), len,
		       prot, flags, off, VMMAP_DIR_HILO, &the_vmarea); 
       if (retval < 0) {
	       return retval;
       }

       tlb_flush_range((uintptr_t)PN_TO_ADDR(the_vmarea->vma_start), len);
       return 0;
}


/*
 * This function implements the munmap(2) syscall.
 *
 * As with do_mmap() it should perform the required error checking,
 * before calling upon vmmap_remove() to do most of the work.
 * Remember to clear the TLB.
 */
int
do_munmap(void *addr, size_t len)
{ 
	KASSERT(NULL != curproc->p_pagedir);
	dbg(DBG_TEST, "KASSERT PASS\n");

	int ret = 0;
	vmmap_t *the_map = NULL;

	if (!PAGE_ALIGNED(addr)) {
		return -EINVAL;
	}
	if (addr < (void *)USER_MEM_LOW || addr >= (void *)USER_MEM_HIGH) {
		return -EINVAL; 
	}
	
	the_map = curproc->p_vmmap;
	ret = vmmap_remove(the_map, ADDR_TO_PN(addr), len);
	if (ret < 0) {
		return ret;
	}

	tlb_flush_range((uintptr_t)addr, len);
	return 0;
}

